package org.json;

import java.math.BigDecimal;
import java.util.Date;
import java.util.LinkedList;

import static java.util.Arrays.*;
import static org.apache.commons.lang.StringUtils.*;

public class JSONPath {

    private JSONObject json;

    public JSONPath(JSONObject json) {
        this.json = json;
    }

    public JSONPath(String json) {
        try {
            this.json = new JSONObject(json);
        } catch (JSONException e) {
            throw new RuntimeException(e);
        }
    }

    public boolean has(String path) {
        try {
            return get(json, extractPathComponents(path)) != null;
        } catch (NodeNotFoundException e) {
            return false;
        }
    }

    public <T> T get(String path) {
        return (T) get(json, extractPathComponents(path));
    }

    public BigDecimal getBigDecimal(String path) {
        Object obj = get(path);
        if (obj instanceof Number) return new BigDecimal(obj.toString());
        if (obj instanceof String) return new BigDecimal((String) obj);
        throw new RuntimeException("The object at [" + path + "] is of type " + obj.getClass().getName() + ", which is not supported");
    }

    public Date getDate(String path) {
        throw new UnsupportedOperationException("Date parsing not supported. Use JodaTime; any other option is hell.");
    }

    protected LinkedList<String> extractPathComponents(String name) {
        if (name.contains("//")) {
            throw new UnsupportedOperationException("Path wildcard '//' not (yet) supported.");
        }

        return new LinkedList<String>(asList(name.split("/")));
    }

    protected Object get(JSONObject currentNode, LinkedList<String> pathComponents) {
        String currentKey = next(pathComponents);
        Object value = getValue(currentNode, currentKey);
        if (pathComponents.isEmpty()) {
            return value;
        } else if (value instanceof JSONObject) {
            return get((JSONObject) value, pathComponents);
        } else if (value instanceof JSONArray) {
            throw new UnindexedArrayException(currentKey);
        }

        throw new NodeNotFoundException(pathComponents.pop());
    }

    private String next(LinkedList<String> pathComponents) {
        while (isBlank(pathComponents.peek())) {
            pathComponents.pop();
        }

        return pathComponents.pop();
    }

    private Object getValue(JSONObject currentNode, String currentKey) {
        if (currentKey.contains("[")) {
            String[] bits = currentKey.split("\\[|\\]");
            String name = bits[0];
            int index = Integer.valueOf(bits[1]) - 1;
            JSONArray array = (JSONArray) safelyGet(name, currentNode);
            return safelyGet(name, index, array);
        } else {
            return safelyGet(currentKey, currentNode);
        }
    }

    private Object safelyGet(String name, int index, JSONArray array) {
        try {
            return array.get(index);
        } catch (JSONException e) {
            throw new NodeNotFoundException(name + "[" + index + "]");
        }
    }

    private Object safelyGet(String name, JSONObject currentNode) {
        try {
            return currentNode.get(name);
        } catch (JSONException e) {
            throw new NodeNotFoundException(name);
        }
    }
}